﻿using UnityEngine;
using Core;
using System.Collections.Generic;
using System;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.UI;

namespace Core
{
    public struct GetUICanvas : IEventData
    {
        public Canvas canvas;
    }

    internal class PanelSystem : EntitySystem, IAwake
    {
        public Canvas UICanvas { get; private set; }

        private IPanelCoverHelper coverOperator;
        private CoreDataObject m_config;
        private EventSystem m_eventSystem;
        Dictionary<PanelLayerType, RectTransform> uiLayerDic = new Dictionary<PanelLayerType, RectTransform>();
        Dictionary<PanelLayerType, Stack<PanelStackData>> panelStackDic = new Dictionary<PanelLayerType, Stack<PanelStackData>>();
        Dictionary<string, BasePanel> openPanelDic = new Dictionary<string, BasePanel>();

        // 界面打开超时时间（毫秒）
        private int openPanelTimeout = 1000;

        public void Awake()
        {
            m_config = new GetCoreData().Publish().coreData;
            coverOperator = GetHelper<IPanelCoverHelper>();
            CreatePanelLayer();

            Subscribe<OnLoadSceneBegin>(OnLoadSceneBegin);
            Subscribe<OpenPanel>(OpenPanel);
            Subscribe<ClosePanel>(ClosePanel);
            Subscribe<CloseLayerPanels>(CloseLayerPanels);
            Subscribe<GetActivePanel>(GetActivePanel);
            Subscribe<GetUICanvas>(GetUICanvas);
            Subscribe<GetEventSystem>(GetEventSystem);
        }
        
        private void GetEventSystem(ref GetEventSystem eventData)
        {
            eventData.eventSystem = m_eventSystem;
        }

        private void GetUICanvas(ref GetUICanvas data)
        {
            data.canvas = UICanvas;
        }

        private void GetActivePanel(ref GetActivePanel data)
        {
            var panel = GetActivePanel(data.path);
            data.panel = panel;
        }

        public void OnLoadSceneBegin(ref OnLoadSceneBegin data)
        {
            CloseAllNormalPanel();
        }

        public void OpenPanel(ref OpenPanel data)
        {
            var path = data.path;
            var arg = data.data;
            new TimeEvent(1, () => { OpenPanel(path, arg); }).Publish();
        }

        public void CloseLayerPanels(ref CloseLayerPanels data)
        {
            var layer = data.layer;
            if (layer == PanelLayerType.None)
                return;

            new TimeEvent(1, () => { CloseLayerPanels(layer); }).Publish();
        }

        public void ClosePanel(ref ClosePanel data)
        {
            var path = data.path;
            new TimeEvent(1, () => { ClosePanel(path); }).Publish();
        }

        public void CreatePanelLayer()
        {
            var initMono = new GetInitMono().Publish().mono;
            if (initMono == null)
            {
                Debug.LogError("game mono is null");
                return;
            }

            var gameTran = initMono.transform;

            // 画布组件
            GameObject canvasObj = new GameObject(nameof(UICanvas));
            canvasObj.layer = m_config.uiMask;
            canvasObj.transform.SetParent(gameTran);
            UICanvas = canvasObj.AddComponent<Canvas>();
            UICanvas.renderMode = RenderMode.ScreenSpaceOverlay;
            canvasObj.AddComponent<GraphicRaycaster>();
            UICanvas.renderMode = RenderMode.ScreenSpaceCamera;
            UICanvas.worldCamera = new GetUICamera().Publish().uiCamera;
            UICanvas.planeDistance = m_config.cameraFar;
            UICanvas.vertexColorAlwaysGammaSpace = true;

            // 缩放组件
            var scaler = canvasObj.AddComponent<CanvasScaler>();
            scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
            scaler.referenceResolution = m_config.referenceResolution;
            scaler.matchWidthOrHeight = m_config.matchWidthOrHeight;

            // 事件组件
            var eventSystemObj = new GameObject("UIEventSystem");
            eventSystemObj.transform.SetParent(gameTran);
            eventSystemObj.AddComponent<InputSystemUIInputModule>();
            m_eventSystem = eventSystemObj.GetComponent<EventSystem>();

            CreateUILayer(PanelLayerType.OnlyBottom);
            CreateUILayer(PanelLayerType.Back);
            CreateUILayer(PanelLayerType.Center);
            CreateUILayer(PanelLayerType.Front);
            CreateUILayer(PanelLayerType.OnlyTop);
        }

        public async void OpenPanel(string panelPath, object arg)
        {
            var asyncOperation = await new LoadAssetAsync<GameObject>(panelPath).PublishAsync();
            var panelPrefab = asyncOperation.asset;
            if (panelPrefab == null)
                return;

            var panelObj = UnityEngine.Object.Instantiate(panelPrefab);
            panelObj.name = panelPath;
            var panel = panelObj.GetComponent<BasePanel>();
            if (panel == null)
            {
                DestroyPanel(panelPath, panelObj);
                Debug.LogError($"there are not BasePanel attach panel object: {panelPath}");
                return;
            }

            panel.panelPath = panelPath;
            panel.AddAssetReference(panelPath);

            var layerType = panel.panelLayer;

            // 最顶层/最底层中，只能存在一个界面
            if (layerType == PanelLayerType.OnlyBottom || layerType == PanelLayerType.OnlyTop)
                CloseLayerPanels(layerType);

            // 打开的界面已开启，则让其与上方的界面全部出栈
            if (IsContainsPanelInLayer(panel))
                CloseAllOverPanel(panel);

            Stack<PanelStackData> stackPanel = null;
            panelStackDic.TryGetValue(layerType, out stackPanel);
            if (stackPanel == null)
            {
                stackPanel = new Stack<PanelStackData>();
                panelStackDic[layerType] = stackPanel;
            }

            // 全屏界面打开，隐藏并冻结后面的界面
            if (panel.IsHaveType(PanelStyleType.Cover))
                coverOperator.CoverPanel(panel.panelLayer, panelStackDic, openPanelDic);

            // 设置变换
            var container = uiLayerDic[layerType];
            panelObj.transform.SetParent(container);
            var rect = (RectTransform)panelObj.transform;
            rect.anchorMin = Vector2.zero;
            rect.anchorMax = Vector3.one;
            rect.offsetMin = Vector2.zero;
            rect.offsetMax = Vector2.zero;
            rect.sizeDelta = Vector2.zero;

            panelObj.transform.localScale = Vector3.one;
            panelObj.transform.localPosition = Vector3.zero;

            // 设置层级
            var layerSorting = (int)layerType * m_config.perLayerSortingLayer;
            if (stackPanel.Count == 0)
            {
                panel.sortingLayer = layerSorting;
            }
            else
            {
                var topPanel = stackPanel.Peek();
                panel.sortingLayer = topPanel.panel.sortingLayer + m_config.perPanelSortingLayer;
            }

            try
            {
                panel.panelState = PanelState.Loading;
                // 先隐藏
                coverOperator.HidePanel(panelObj.gameObject);

                // 超时检测
                var timeUnit = new TimeEvent(openPanelTimeout, 
                    () => { new OnOpenPanelTimeout(panelPath).Publish(); })
                    .Bind(panel).Publish().timeUnit;

                panel._OnInit(arg);
                await panel._OnInitAsync(arg);

                PackPanelTexture(panel);

                timeUnit.Stop();

                // 初始化完毕再显示
                coverOperator.ShowPanel(panelObj.gameObject);

                panel.panelState = PanelState.Show;

                stackPanel.Push(new PanelStackData(panel, arg));
                openPanelDic[panelPath] = panel;

                new OnOpenPanelEnd() { path = panelPath }.Publish();
            }
            catch (Exception e)
            {
                DestroyPanel(panelPath, panelObj);
                Debug.LogError(e);
            }
        }

        public void PackPanelTexture(BasePanel panel)
        {
            var imgExs = panel.GetComponentsInChildren<ImageEx>();
            var imgList = new List<ImageEx>();
            foreach (var imgEx in imgExs)
            {
                if (string.IsNullOrEmpty(imgEx.imageTexturePath) 
                    || imgEx.img == null 
                    || imgEx.img.sprite == null 
                    || imgEx.img.sprite.texture == null)
                    continue;
                imgList.Add(imgEx);
                new PackerImageEx(imgEx).Publish();
            }

            if (imgList.Count == 0)
                return;

            new ProcessAssetPacker().Publish();
        }

        public BasePanel GetActivePanel(string panelPath)
        {
            BasePanel panel;
            openPanelDic.TryGetValue(panelPath, out panel);
            return panel;
        }

        public void ClosePanel(string panelPath)
        {
            BasePanel panel = null;
            openPanelDic.TryGetValue(panelPath, out panel);

            if (panel == null) { return; }

            CloseAllOverPanel(panel);
        }

        private bool IsContainsPanelInLayer(BasePanel panel)
        {
            Stack<PanelStackData> stackPanel = null;
            panelStackDic.TryGetValue(panel.panelLayer, out stackPanel);
            if (stackPanel == null) { return false; }

            foreach (var item in stackPanel)
            {
                if (item.panel.panelPath == panel.panelPath)
                    return true;
            }

            return false;
        }

        private void CloseAllOverPanel(BasePanel panel)
        {
            Stack<PanelStackData> stackPanel = null;
            panelStackDic.TryGetValue(panel.panelLayer, out stackPanel);

            if (!IsContainsPanelInLayer(panel)) { return; }

            var path = panel.panelPath;
            while (stackPanel.Count > 0)
            {
                var curPath = CloseTopStackPanel(stackPanel);

                if (curPath == path) { break; }
            }
        }

        public void CloseLayerPanels(PanelLayerType uiPlayer)
        {
            Stack<PanelStackData> stackPanel = null;
            panelStackDic.TryGetValue(uiPlayer, out stackPanel);
            
            if (stackPanel == null) 
                return;

            while (stackPanel.Count > 0)
            {
                CloseTopStackPanel(stackPanel);
            }
        }

        string CloseTopStackPanel(Stack<PanelStackData> stackPanel)
        {
            var curPanel = stackPanel.Pop();
            var panel = curPanel.panel;
            var panelPath = panel.panelPath;

            openPanelDic.Remove(panel.panelPath);
            panel._Close();

            if (panel.IsHaveType(PanelStyleType.Cover))
                coverOperator.PopPanel(panel.panelLayer, panelStackDic, openPanelDic);

            DestroyPanel(panelPath, panel.gameObject);

            new OnClosePanelEnd() { path = panelPath }.Publish();

            return panelPath;
        }

        private void DestroyPanel(string path, GameObject panelObj)
        {
            GameObject.DestroyImmediate(panelObj);
        }

        private void CreateUILayer(PanelLayerType uiLayer)
        {
            var layer = new GameObject(uiLayer.ToString());
            layer.transform.SetParent(UICanvas.transform);
            layer.layer = m_config.uiMask;
            var rect = layer.AddComponent<RectTransform>();

            rect.anchorMin = Vector2.zero;
            rect.anchorMax = Vector2.one;
            rect.offsetMin = Vector2.zero;
            rect.offsetMax = Vector2.zero;

            rect.localScale = Vector3.one;
            rect.localPosition = Vector3.zero;
            //rect.localPosition = new Vector3(0, 0, (int)uiLayer * -m_config.perLayerDistance);

            uiLayerDic[uiLayer] = rect;
        }

        public void CloseAllNormalPanel()
        {
            CloseLayerPanels(PanelLayerType.Back);
            CloseLayerPanels(PanelLayerType.Center);
            CloseLayerPanels(PanelLayerType.Front);
        }
    }

    public enum PanelLayerType
    {
        None,
        /// <summary>
        /// 最底层(唯一)
        /// </summary>
        OnlyBottom,
        /// <summary>
        /// 后一层
        /// </summary>
        Back,
        /// <summary>
        /// 中间层
        /// </summary>
        Center,
        /// <summary>
        /// 前一层
        /// </summary>
        Front,
        /// <summary>
        /// 最顶层(唯一)
        /// </summary>
        OnlyTop
    }

    public enum PanelState
    {
        None,
        Loading,
        Show,
        Freeze,
        Hide,
    }

    public class PanelStackData
    {
        public BasePanel panel;
        public object openArgs;

        public PanelStackData(BasePanel panel, object args)
        {
            this.panel = panel;
            openArgs = args;
        }
    }

    public struct GetEventSystem : IEventData
    {
        public EventSystem eventSystem;
    }

    public struct OnOpenPanelTimeout : IEventData
    {
        public string panelPath;
        public OnOpenPanelTimeout(string panelPath)
        {
            this.panelPath = panelPath;
        }
    }
}

